package com.youlin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlin.entities.*;
import com.youlin.security.SecurityUser;
import com.youlin.dto.*;
import com.youlin.dto.res.AdminResDto;
import com.youlin.dto.req.EditAdminReqDto;
import com.youlin.mapper.BygUnitAdminMapper;
import com.youlin.service.BygAdminService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlin.service.BygAppInfoService;
import com.youlin.service.BygAppointmentService;
import com.youlin.service.BygRoleService;
import com.youlin.untils.*;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Service
public class BygAdminServiceImpl extends ServiceImpl<BygUnitAdminMapper, BygAdmin> implements BygAdminService {

    @Value("${spring.profiles.active}")
    private String active;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private BygRoleService bygRoleService;
    @Autowired
    private BygAppointmentService bygAppointmentService;
    @Autowired
    private SessionRegistry sessionRegistry;
    @Autowired
    private BygAppInfoService bygAppInfoService;

    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        BygAdmin admin = getOne(Wrappers.lambdaQuery(BygAdmin.class).eq(BygAdmin::getName, name));
        if (admin == null) throw new UsernameNotFoundException("登录账号错误");
        BygRole role = bygRoleService.getById(admin.getRoleId());
        BygAppInfo appInfo = bygAppInfoService.getById(admin.getUnitId());
        if (appInfo == null) return new SecurityUser(admin.setRole(role));
        return new SecurityUser(admin.setRole(role), appInfo.getAppName());
    }

    @Override
    public PageDataResDto<List<AdminResDto>> findAllAdmins(Integer unitId, Integer roleId, PageInfo pageInfo) {
        QueryWrapper<BygAdmin> queryWrapper = new QueryWrapper<BygAdmin>()
                .eq("unit_id", unitId).orderByDesc("create_time");
        if (roleId != null) queryWrapper.eq("role_id", roleId);
        IPage<BygAdmin> page = page(new Page<>(pageInfo.getPage(), pageInfo.getSize()), queryWrapper);
        List<AdminResDto> bygAdmins = BaseService.findAndDealWithOneToOne(page.getRecords(),
                BygAdmin::getRoleId, AdminResDto.class, bygRoleService, BygRole::getId);
        pageInfo = new PageInfo(page.getCurrent(), page.getSize(), page.getTotal());
        return new PageDataResDto<List<AdminResDto>>(Status.SUCCESS).setData(bygAdmins).setPageInfo(pageInfo);
    }

    @Override
    @Transactional
    public BaseResDto<Status, String> updPwd(Integer adminId, String oldPwd, String newPwd) {
        if (!newPwd.matches("^(?=.*[a-zA-Z])(?=.*\\d)[a-zA-Z\\d]{8,20}$"))
            return new BaseResDto<>(Status.NEWPWDERROR);
        BygAdmin admin = getById(adminId).setReset(false);
        if (admin == null) return new BaseResDto<>(Status.PARAMETERERROR);
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        if (!encoder.matches(oldPwd, admin.getPwd()))
            return new BaseResDto<>(Status.OLDPWDERROR);
        if (!updateById(admin.setPwd(encoder.encode(newPwd))))
            return new BaseResDto<>(Status.ERROR);
        BygRole role = bygRoleService.getById(admin.getRoleId());
        AdminUtil.setCurrentAdmin(new SecurityUser(admin.setRole(role)));
        return new BaseResDto<>(Status.SUCCESS);
    }

    @Override
    public BaseDataResDto<String> getCaptchaBase64() {
        String captchaBase64;
        try {
            captchaBase64 = CaptchaUtil.getCaptchaBase64(redisTemplate);
        } catch (IOException e) {
            return new BaseDataResDto<>(Status.ERROR);
        }
        if (StringUtils.isBlank(captchaBase64)) return new BaseDataResDto<>(Status.ERROR);
        return new BaseDataResDto<String>(Status.SUCCESS).setData(captchaBase64);
    }

    @Override
    public BaseResDto<Status, String> validateCaptcha(String code) {
        if (!CaptchaUtil.validateCaptcha(redisTemplate, code))
            return new BaseResDto<>(Status.ERROR);
        redisTemplate.delete("captcha:" + code);
        return new BaseResDto<>(Status.SUCCESS);
    }

    @Override
    @Transactional
    public BaseResDto<Status, String> saveAdmin(EditAdminReqDto data) {
        // 异步查询 验证账号是否全局唯一
        CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> {
            String name = data.getRoleId().intValue() == MyEnum.ROLE_REVIEWER_ID.getValue() ? "A" : "B";
            for (int i = 1; i <= 100; i++) {
                int random = new Random().ints(10000, 99999).findAny().getAsInt();
                LambdaQueryWrapper<BygAdmin> wrapper = Wrappers
                        .lambdaQuery(BygAdmin.class).eq(BygAdmin::getName, name + random);
                if (count(wrapper) == 0) {
                    name += random;
                    break;
                }
            }
            return name;
        });

        // 异步查询 验证账号使用者姓名是否全局唯一
        CompletableFuture<Boolean> nickFuture = CompletableFuture.supplyAsync(() ->
                count(Wrappers.lambdaQuery(BygAdmin.class)
                        .eq(BygAdmin::getUnitId, data.getUnitId())
                        .eq(BygAdmin::getNick, data.getNick())) > 0);

        return nickFuture.thenCombine(nameFuture, (nickCheck, name) -> {
            if (nickCheck) return new BaseResDto<>(Status.NICKDUPLICATE);
            String pwd = new BCryptPasswordEncoder().encode("123456");
            BygAdmin admin = MyBeanUtil.copyProperties(data, BygAdmin.class)
                    .setName(name).setPwd(pwd).setCreateTime(LocalDateTime.now());
            if (save(admin)) return new BaseResDto<>(Status.SUCCESS);
            return new BaseResDto<>(Status.ERROR);
        }).join();
    }

    @Override
    public BaseResDto<Status, String> editAdmin(EditAdminReqDto data) {
        BygAdmin admin = new BygAdmin().setId(data.getId()).setTel(data.getTel());
        LambdaQueryWrapper<BygAdmin> wrapper = Wrappers
                .lambdaQuery(BygAdmin.class).eq(BygAdmin::getUnitId, data.getUnitId())
                .ne(BygAdmin::getId, data.getId()).eq(BygAdmin::getNick, data.getNick());
        if (count(wrapper) > 0) return new BaseResDto<>(Status.NICKDUPLICATE);
        admin.setNick(data.getNick()).setPid(data.getPid());
        if (BooleanUtils.isTrue(data.getResetPwd()))
            admin.setPwd(new BCryptPasswordEncoder().encode("123456")).setReset(true);
        if (!updateById(admin)) return new BaseResDto<>(Status.ERROR);

        if (BooleanUtils.isTrue(data.getResetPwd())) {
            CompletableFuture.runAsync(() -> {
                BygAdmin bygAdmin = getById(data.getId());
                BygRole role = bygRoleService.getById(bygAdmin.getRoleId());
                SecurityUser securityUser = new SecurityUser(bygAdmin.setRole(role));
                AdminUtil.removeUser(sessionRegistry, securityUser);
            });
        }
        return new BaseResDto<>(Status.SUCCESS);
    }

    @Override
    @Transactional
    public BaseResDto<Status, String> upDownAdmin(Integer adminId) {
        BygAdmin admin = getById(adminId);
        if (!updateById(admin.setUsable(!admin.getUsable())))
            return new BaseResDto<>(Status.ERROR);

        // 停用账号，移除该账号在线的用户
        if (BooleanUtils.isFalse(admin.getUsable())) {
            CompletableFuture.runAsync(() -> {
                BygRole role = bygRoleService.getById(admin.getRoleId());
                SecurityUser securityUser = new SecurityUser(admin.setRole(role));
                AdminUtil.removeUser(sessionRegistry, securityUser);
            });
        }
        return new BaseResDto<>(Status.SUCCESS);
    }

    @Override
    public BaseResDto<Status, String> checkAdmin(Integer adminId) {
        QueryWrapper<BygAppointment> queryWrapper = new QueryWrapper<BygAppointment>()
                .nested(qw -> qw.eq("operator_id", adminId)
                        .in("status", Arrays.asList(MyEnum.AM_CREATE.getValue(), MyEnum.AM_OPERATE.getValue())))
                .or(qw -> qw.eq("reviewer_id", adminId).eq("status", MyEnum.AM_REVIEWING.getValue()));
        List<BygAppointment> appointments = bygAppointmentService.list(queryWrapper);
        if (appointments.size() > 0) return new BaseResDto<>(Status.UNDONEWORK);
        return new BaseResDto<>(Status.SUCCESS);
    }

    @Override
    public BaseResDto<Status, String> sendCode(String name, String tel) {
        if (!active.equals("prod")) return new BaseResDto<>(Status.SUCCESS);

        String sendCodeLock1 = "sendCodeLock1:" + tel;
        // 防止表单重复提交
        if (!redisTemplate.opsForValue().setIfAbsent(sendCodeLock1, 123))
            return new BaseResDto<>(Status.SENDCODEERROR);
        try {
            LambdaQueryWrapper<BygAdmin> wrapper = Wrappers.lambdaQuery(BygAdmin.class)
                    .eq(BygAdmin::getName, name).eq(BygAdmin::getTel, tel);
            if (count(wrapper) == 0) {
                redisTemplate.delete(sendCodeLock1);
                return new BaseResDto<>(Status.ADMINTELERROR);
            }
            Integer code = smsUtil.sendSms(tel, SMSUtil.Template.TAKERLOGIN);

            // 将验证码缓存限时缓存入Redis
            RedisUtil.setObjectExpire(redisTemplate, "adminTel:" + tel, code, 5, TimeUnit.MINUTES);
            return new BaseResDto<>(Status.SUCCESS);
        } catch (SmsLimitExpectation e) {
            return new BaseResDto<>(Status.SMSLIMITERROR);
        } catch (SmsErrorExpectation e) {
            return new BaseResDto<>(Status.SMSERROR);
        } catch (Exception e) {
            return new BaseResDto<>(Status.ERROR);
        } finally {
            // 定时解锁 Redis
            redisTemplate.expire(sendCodeLock1, 60, TimeUnit.SECONDS);
        }
    }

}
